| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706 |
- "use client";
- import { useTranslations } from "next-intl";
- import { Link } from "@/i18n/navigation";
- import { useAuth } from "@/providers/auth-provider";
- import { useEffect, useState } from "react";
- import { Wallet, BookOpen, FileText, History, LogOut, ChevronRight, Settings, UserRoundPen } from "lucide-react";
- import { fetchWalletBalance } from "@/lib/account-api";
- import {
- fetchBalanceRecordList,
- type BalanceRecord,
- } from "@/lib/balance-record-api";
- import { fetchCustomUserInfo, tryFetchCustomUserInfo, updateUserInfo } from "@/lib/user-info-api";
- import {
- cancelOrder,
- fetchOrderList,
- getOrderStatusLabel,
- type OrderRecord,
- } from "@/lib/order-api";
- import {
- fetchPurchasedCourses,
- type PurchasedCourse,
- } from "@/lib/goods-order-api";
- import {
- fetchWithdrawalList,
- getWithdrawalStatusLabel,
- type WithdrawalRecord,
- } from "@/lib/withdrawal-api";
- import { cn } from "@/lib/utils";
- import { ReferralCodeBadge } from "@/components/referral-code-badge";
- import { AccountLoginRequired } from "@/components/auth/account-login-required";
- export default function AccountPage() {
- const t = useTranslations("account");
- const { user, isReady, updateProfile, logout } = useAuth();
-
- // 核心状态
- const [walletBalance, setWalletBalance] = useState<number | null>(null);
- const [walletLoading, setWalletLoading] = useState(false);
- const [balanceDetailOpen, setBalanceDetailOpen] = useState(false);
- const [balanceDetailLoading, setBalanceDetailLoading] = useState(false);
- const [balanceDetailError, setBalanceDetailError] = useState<string | null>(
- null,
- );
- const [balanceRecords, setBalanceRecords] = useState<BalanceRecord[]>([]);
- const [purchasedCourses, setPurchasedCourses] = useState<PurchasedCourse[]>([]);
- const [orders, setOrders] = useState<OrderRecord[]>([]);
- const [ordersLoading, setOrdersLoading] = useState(false);
- const [withdrawals, setWithdrawals] = useState<WithdrawalRecord[]>([]);
- const [withdrawalsLoading, setWithdrawalsLoading] = useState(false);
-
- const [cancelTarget, setCancelTarget] = useState<OrderRecord | null>(null);
- const [cancelLoading, setCancelLoading] = useState(false);
- const [editInfoOpen, setEditInfoOpen] = useState(false);
- const [editName, setEditName] = useState("");
- const [editPhone, setEditPhone] = useState("");
- const [editIdentity, setEditIdentity] = useState("");
- const [editSubmitting, setEditSubmitting] = useState(false);
- const [editError, setEditError] = useState<string | null>(null);
- const [logoutConfirmOpen, setLogoutConfirmOpen] = useState(false);
- const [referralCode, setReferralCode] = useState<string | null>(null);
- useEffect(() => {
- if (!user) return;
- let cancelled = false;
- async function loadData() {
- setWalletLoading(true);
- setOrdersLoading(true);
- setWithdrawalsLoading(true);
- try {
- const [balanceResult, orderResult, courseResult, withdrawResult] =
- await Promise.allSettled([
- fetchWalletBalance(),
- fetchOrderList({ current: 1, row: 10 }),
- fetchPurchasedCourses(),
- fetchWithdrawalList(),
- ]);
- if (cancelled) return;
- if (balanceResult.status === "fulfilled") {
- setWalletBalance(balanceResult.value);
- }
- if (orderResult.status === "fulfilled") {
- setOrders(orderResult.value.list);
- }
- if (courseResult.status === "fulfilled") {
- setPurchasedCourses(courseResult.value);
- } else {
- console.error(courseResult.reason);
- }
- if (withdrawResult.status === "fulfilled") {
- setWithdrawals(withdrawResult.value.list);
- }
- } catch (e) {
- console.error(e);
- } finally {
- if (!cancelled) {
- setWalletLoading(false);
- setOrdersLoading(false);
- setWithdrawalsLoading(false);
- }
- }
- }
- loadData();
- return () => { cancelled = true; };
- }, [user]);
- useEffect(() => {
- if (!user?.email) return;
- let cancelled = false;
- void tryFetchCustomUserInfo().then((info) => {
- if (cancelled || !info) return;
- if (info.referralCode) setReferralCode(info.referralCode);
- if (info.levelLabel && !user.levelLabel) {
- updateProfile({ levelLabel: info.levelLabel });
- }
- });
- return () => { cancelled = true; };
- // 仅登录用户变化时拉取,避免 updateProfile 触发重复请求
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [user?.email, user?.levelLabel]);
- async function openBalanceDetail() {
- setBalanceDetailOpen(true);
- setBalanceDetailLoading(true);
- setBalanceDetailError(null);
- try {
- const list = await fetchBalanceRecordList({ current: 1, row: 50 });
- setBalanceRecords(list);
- } catch (e) {
- setBalanceRecords([]);
- setBalanceDetailError((e as Error)?.message || "消费明细加载失败,请稍后重试");
- } finally {
- setBalanceDetailLoading(false);
- }
- }
- function balanceTypeLabel(type: BalanceRecord["type"]) {
- return type === 2 ? "奖励" : "取款";
- }
- function openEditInfo() {
- if (!user) return;
- setEditError(null);
- setEditName(user.name ?? "");
- setEditPhone(user.phone ?? "");
- setEditIdentity(user.identity ?? "");
- setEditInfoOpen(true);
- }
- function closeEditInfo() {
- setEditInfoOpen(false);
- setEditSubmitting(false);
- setEditError(null);
- }
- async function submitEditInfo() {
- const name = editName.trim();
- const phone = editPhone.trim();
- const identity = editIdentity.trim();
- if (!name) {
- setEditError("请输入姓名");
- return;
- }
- setEditSubmitting(true);
- setEditError(null);
- try {
- await updateUserInfo({ name, phone, identity });
- const info = await fetchCustomUserInfo();
- updateProfile({
- name: info.name || name,
- phone: info.phone || phone,
- identity: info.identity || identity,
- });
- closeEditInfo();
- if (typeof window !== "undefined") window.location.reload();
- } catch (e) {
- setEditError((e as Error)?.message || "修改失败,请稍后重试");
- } finally {
- setEditSubmitting(false);
- }
- }
- async function handleConfirmCancel() {
- if (!cancelTarget) return;
- try {
- setCancelLoading(true);
- await cancelOrder(cancelTarget.id);
- const res = await fetchOrderList({ current: 1, row: 10 });
- setOrders(res.list);
- setCancelTarget(null);
- } catch {
- alert("取消失败,请重试");
- } finally {
- setCancelLoading(false);
- }
- }
- // 高级状态色系:针对黑底优化
- function getStatusStyle(status: OrderRecord["status"] | WithdrawalRecord["status"]) {
- const s = String(status).trim();
- const sLower = s.toLowerCase();
- // 已完成:绿色
- if (s === "2" || sLower === "completed" || sLower === "success") {
- return "text-emerald-400 border-emerald-400/30 bg-emerald-400/10";
- }
- // 已拒绝/已失败:红色(含:3/4/5 以及 failed/rejected/cancelled 字符串)
- if (
- s === "3" || // 已拒绝 / 支付失败(支付失败在 order-api 中为 3)
- s === "4" ||
- s === "5" ||
- sLower === "failed" ||
- sLower === "rejected" ||
- sLower === "cancelled"
- ) {
- return "text-rose-400 border-rose-400/30 bg-rose-400/10";
- }
- return "text-amber-400 border-amber-400/30 bg-amber-400/10";
- }
- if (!isReady) return <div className="min-h-screen bg-[#050b14] flex items-center justify-center text-slate-500">加载中...</div>;
- if (!user) {
- return <AccountLoginRequired />;
- }
- return (
- <div className="min-h-screen bg-[#050b14] pb-24 text-slate-300 font-sans">
- {/* 全局背景装饰 */}
- <div className="pointer-events-none fixed inset-0 z-0">
- <div className="absolute left-1/4 top-0 h-[500px] w-[500px] rounded-full bg-blue-900/10 blur-[120px]" />
- <div className="absolute right-1/4 bottom-0 h-[500px] w-[500px] rounded-full bg-[#b89458]/5 blur-[120px]" />
- </div>
- <div className="relative z-10 site-container pt-12">
- {/* 1. 欢迎区 & 统计卡片 (全玻璃质感) */}
- <section className="ui-enter mb-10 rounded-[2.5rem] border border-white/10 bg-white/5 p-8 backdrop-blur-2xl shadow-2xl">
- <div className="flex flex-col gap-8 md:flex-row md:items-center md:justify-between">
- <div>
- <p className="text-xs font-bold tracking-[0.3em] text-[#b89458] mb-2 uppercase">User Control Center</p>
- <div className="flex flex-wrap items-center gap-x-4 gap-y-2">
- <h1 className="font-serif text-3xl font-bold text-white md:text-5xl tracking-wide">
- {t("welcome", { name: user.name })}
- </h1>
- {referralCode ? (
- <ReferralCodeBadge code={referralCode} />
- ) : null}
- </div>
- <p className="mt-3 text-sm text-slate-400">欢迎回来,在这里掌控您的个人资产与学习进程。</p>
- </div>
- <Link
- href="/account/withdraw-apply"
- className="ui-interactive-btn flex items-center gap-2 rounded-2xl bg-gradient-to-br from-[#f3deae] to-[#d9be88] px-8 py-4 text-sm font-bold text-[#5c461a] shadow-xl shadow-[#b89458]/10"
- >
- <Wallet size={18} /> 发起领取申请
- </Link>
- </div>
- <div className="mt-12 grid grid-cols-2 gap-4 md:grid-cols-4 lg:gap-6">
- <button
- type="button"
- onClick={openBalanceDetail}
- className="group rounded-2xl border border-white/5 bg-white/5 p-6 text-center transition-all hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-[#b89458]/40"
- >
- <div className="flex flex-col items-center gap-3 mb-4">
- <div
- className={cn(
- "p-2 rounded-xl bg-white/5 group-hover:scale-110 transition-transform text-[#f3deae]",
- )}
- >
- <Wallet size={18} />
- </div>
- <span className="text-[11px] font-bold uppercase tracking-widest text-slate-500">
- 钱包余额
- </span>
- </div>
- <p className="text-3xl font-bold tracking-tight text-white">
- {walletLoading ? "..." : `$${(walletBalance ?? 0).toFixed(2)}`}
- </p>
- <p className="mt-2 text-[11px] font-semibold text-slate-500">
- 点击查看消费明细
- </p>
- </button>
- {[
- { label: "已购课程", value: purchasedCourses.length, icon: BookOpen, color: "text-sky-400" },
- { label: "订单总数", value: orders.length, icon: FileText, color: "text-emerald-400" },
- { label: "领取记录", value: withdrawals.length, icon: History, color: "text-teal-400" }
- ].map((stat, i) => (
- <div
- key={i}
- className="group rounded-2xl border border-white/5 bg-white/5 p-6 text-center transition-all hover:bg-white/10"
- >
- <div className="flex flex-col items-center gap-3 mb-4">
- <div className={cn("p-2 rounded-xl bg-white/5 group-hover:scale-110 transition-transform", stat.color)}>
- <stat.icon size={18} />
- </div>
- <span className="text-[11px] font-bold uppercase tracking-widest text-slate-500">{stat.label}</span>
- </div>
- <p className="text-3xl font-bold tracking-tight text-white">{stat.value}</p>
- </div>
- ))}
- </div>
- </section>
- <div className="grid gap-8 lg:grid-cols-[300px_minmax(0,1fr)]">
- {/* 2. 侧边导航 */}
- <aside className="ui-enter flex flex-col gap-6">
- <div className="rounded-[2.5rem] border border-white/10 bg-white/5 p-6 backdrop-blur-2xl shadow-xl">
- <div className="flex items-center gap-4 mb-10 p-2">
- <div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-[1.25rem] bg-gradient-to-br from-slate-800 to-slate-900 text-xl font-bold text-[#f3deae] shadow-lg ring-1 ring-white/10">
- {(user.name || "U").slice(0, 1).toUpperCase()}
- </div>
- <div className="min-w-0">
- <p className="truncate text-lg font-bold text-white">{user.name}</p>
- <p className="text-xs font-medium text-slate-500 tracking-wide uppercase">Member Account</p>
- </div>
- </div>
-
- <nav className="flex flex-col gap-2">
- {[
- { label: "我的课程", href: "/account/purchased-courses", icon: BookOpen },
- { label: "订单管理", href: "/account/orders", icon: FileText },
- { label: "领取记录", href: "/account/withdrawals", icon: History },
- { label: "修改信息", href: "#edit-info", icon: UserRoundPen, onClick: openEditInfo },
- { label: "修改密码", href: "/account/change-password", icon: Settings }
- ].map((item) =>
- "onClick" in item ? (
- <button
- key={item.href}
- type="button"
- onClick={item.onClick}
- className="group flex w-full items-center justify-between rounded-2xl px-5 py-4 text-sm font-semibold text-slate-400 transition-all hover:bg-white/10 hover:text-white"
- >
- <span className="flex items-center gap-4">
- <item.icon
- size={20}
- className="text-slate-500 group-hover:text-[#f3deae]"
- />{" "}
- {item.label}
- </span>
- <ChevronRight
- size={16}
- className="opacity-0 -translate-x-2 transition-all group-hover:opacity-100 group-hover:translate-x-0"
- />
- </button>
- ) : (
- <Link
- key={item.href}
- href={item.href}
- className="group flex items-center justify-between rounded-2xl px-5 py-4 text-sm font-semibold text-slate-400 transition-all hover:bg-white/10 hover:text-white"
- >
- <span className="flex items-center gap-4">
- <item.icon
- size={20}
- className="text-slate-500 group-hover:text-[#f3deae]"
- />{" "}
- {item.label}
- </span>
- <ChevronRight
- size={16}
- className="opacity-0 -translate-x-2 transition-all group-hover:opacity-100 group-hover:translate-x-0"
- />
- </Link>
- ),
- )}
- </nav>
- <div className="mt-10 border-t border-white/5 pt-6">
- <button
- type="button"
- onClick={() => setLogoutConfirmOpen(true)}
- className="flex w-full items-center gap-4 rounded-2xl px-5 py-4 text-sm font-bold text-rose-400 transition-all hover:bg-rose-500/10"
- >
- <LogOut size={20} /> 退出登录
- </button>
- </div>
- </div>
- </aside>
- {/* 3. 主内容区 */}
- <div className="flex flex-col gap-8">
- {/* 订单记录卡片 */}
- <section className="ui-enter ui-enter-delay-1 rounded-[2.5rem] border border-white/10 bg-white/5 p-8 backdrop-blur-2xl shadow-xl">
- <div className="flex items-center justify-between mb-8">
- <h2 className="font-serif text-2xl font-bold text-white">最新订单记录</h2>
- <Link href="/account/orders" className="text-xs font-bold text-[#b89458] hover:text-[#f3deae] transition-colors">查看全部</Link>
- </div>
-
- {ordersLoading ? (
- <div className="py-12 flex justify-center text-slate-500">数据同步中...</div>
- ) : orders.length === 0 ? (
- <div className="py-12 flex flex-col items-center justify-center rounded-3xl border border-dashed border-white/10 bg-white/5 text-slate-500">
- <p className="text-sm">{t("noOrders")}</p>
- </div>
- ) : (
- <div className="space-y-4">
- {orders.slice(0, 2).map((o) => (
- <div key={o.serial} className="group rounded-[1.5rem] border border-white/5 bg-white/5 p-5 transition-all hover:bg-white/10">
- <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
- <div className="flex items-center gap-5">
- <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-white/5 text-[#f3deae]">
- <FileText size={22} />
- </div>
- <div>
- <p className="text-[15px] font-bold text-white group-hover:text-[#f3deae] transition-colors">{o.details}</p>
- <p className="text-xs font-medium text-slate-500 mt-1">NO: {o.serial}</p>
- </div>
- </div>
- <div className="flex shrink-0 items-center justify-end gap-4 sm:gap-6">
- <div className="flex flex-col items-end gap-1.5">
- <p className="text-xl font-bold text-white tracking-tight">${o.amount}</p>
- <span className={cn("rounded-full border px-3 py-1 text-[10px] font-bold", getStatusStyle(o.status))}>
- {getOrderStatusLabel(o.status)}
- </span>
- </div>
- {o.status === 1 && (
- <button onClick={() => setCancelTarget(o)} className="text-xs font-bold text-rose-400 hover:text-rose-300">取消</button>
- )}
- </div>
- </div>
- </div>
- ))}
- </div>
- )}
- </section>
- {/* 领取记录卡片 (还原显示) */}
- <section className="ui-enter ui-enter-delay-2 rounded-[2.5rem] border border-white/10 bg-white/5 p-8 backdrop-blur-2xl shadow-xl">
- <div className="flex items-center justify-between mb-8">
- <h2 className="font-serif text-2xl font-bold text-white">奖学金领取记录</h2>
- <Link href="/account/withdrawals" className="text-xs font-bold text-[#b89458] hover:text-[#f3deae] transition-colors">查看全部记录</Link>
- </div>
-
- {withdrawalsLoading ? (
- <div className="py-12 flex justify-center text-slate-500">记录检索中...</div>
- ) : withdrawals.length === 0 ? (
- <div className="py-12 flex flex-col items-center justify-center rounded-3xl border border-dashed border-white/10 bg-white/5 text-slate-500">
- <p className="text-sm">暂无领取记录</p>
- </div>
- ) : (
- <div className="space-y-4">
- {withdrawals.slice(0, 2).map((w) => (
- <div key={w.serial} className="group rounded-[1.5rem] border border-white/5 bg-white/5 p-5 transition-all hover:bg-white/10">
- <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
- <div className="flex items-center gap-5">
- <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-white/5 text-teal-400">
- <History size={22} />
- </div>
- <div>
- <p className="text-[15px] font-bold text-white group-hover:text-teal-400 transition-colors">{w.details}</p>
- <p className="text-xs font-medium text-slate-500 mt-1">流水号: {w.serial}</p>
- </div>
- </div>
- <div className="flex shrink-0 flex-col items-end gap-1.5">
- <p className="text-xl font-bold text-white tracking-tight">${w.amount}</p>
- <span className={cn("rounded-full border px-3 py-1 text-[10px] font-bold", getStatusStyle(w.status))}>
- {getWithdrawalStatusLabel(w.status)}
- </span>
- </div>
- </div>
- </div>
- ))}
- </div>
- )}
- </section>
- {/* 已购买课程 */}
- <section className="ui-enter ui-enter-delay-3 rounded-[2.5rem] border border-white/10 bg-white/5 p-8 backdrop-blur-2xl shadow-xl">
- <div className="flex items-center justify-between mb-8">
- <h2 className="font-serif text-2xl font-bold text-white">已购课程</h2>
- <Link href="/account/purchased-courses" className="text-xs font-bold text-[#b89458] hover:text-[#f3deae] transition-colors">浏览全部课程</Link>
- </div>
-
- <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
- {purchasedCourses.length > 0 ? (
- purchasedCourses.slice(0, 3).map((c) => (
- <Link key={c.id} href={`/courses/${c.goodsId}${c.goodsType === 5 ? '?cat=strategy' : ''}`} className="group relative flex flex-col overflow-hidden rounded-[2rem] border border-white/5 bg-white/5 transition-all hover:-translate-y-1 hover:bg-white/10">
- <div className="relative h-40 w-full overflow-hidden">
- {c.coverUrl ? (
- <img src={c.coverUrl} alt={c.title} className="h-full w-full object-cover transition-transform duration-700 group-hover:scale-110" />
- ) : <div className="h-full w-full bg-slate-800" />}
- <div className="absolute inset-0 bg-gradient-to-t from-[#050b14] to-transparent opacity-60" />
- </div>
- <div className="p-6">
- <h3 className="line-clamp-1 text-[15px] font-bold text-white group-hover:text-[#f3deae] transition-colors">{c.title}</h3>
- </div>
- </Link>
- ))
- ) : (
- <div className="col-span-full py-12 flex items-center justify-center rounded-3xl border border-dashed border-white/10 bg-white/5 text-sm font-medium text-slate-500">
- 暂无学习中的课程
- </div>
- )}
- </div>
- </section>
- </div>
- </div>
- </div>
- {/* 黑金风格确认弹窗 */}
- {cancelTarget && (
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
- <div className="w-full max-w-sm overflow-hidden rounded-[2.5rem] border border-white/10 bg-[#0a1120] shadow-2xl">
- <div className="p-10 text-center">
- <div className="mx-auto mb-6 flex h-20 w-20 items-center justify-center rounded-full bg-rose-500/10 text-rose-400">
- <FileText size={32} />
- </div>
- <h3 className="text-xl font-bold text-white">确认撤销订单?</h3>
- <p className="mt-3 text-sm leading-relaxed text-slate-400 px-4">
- 订单流水号 <span className="text-slate-200">{cancelTarget.serial}</span> 撤销后该操作不可恢复。
- </p>
- </div>
- <div className="flex border-t border-white/5">
- <button onClick={() => setCancelTarget(null)} className="flex-1 py-5 text-sm font-bold text-slate-400 hover:bg-white/5">暂不处理</button>
- <div className="w-px bg-white/5" />
- <button onClick={handleConfirmCancel} disabled={cancelLoading} className="flex-1 py-5 text-sm font-bold text-rose-400 hover:bg-rose-500/10 disabled:opacity-50">
- {cancelLoading ? "执行中..." : "确认撤销"}
- </button>
- </div>
- </div>
- </div>
- )}
- {/* 消费明细弹框 */}
- {balanceDetailOpen && (
- <div className="fixed inset-0 z-[55] flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
- <div className="w-full max-w-2xl overflow-hidden rounded-[2.5rem] border border-white/10 bg-[#0a1120] shadow-2xl">
- <div className="flex items-center justify-between border-b border-white/5 p-6 md:px-8">
- <h3 className="text-xl font-bold text-white flex items-center gap-2">
- <Wallet className="text-[#b89458]" size={20} /> 消费明细
- </h3>
- <button
- onClick={() => setBalanceDetailOpen(false)}
- className="text-sm font-bold text-slate-500 hover:text-white"
- >
- 关闭
- </button>
- </div>
- <div className="p-6 md:p-8 max-h-[70vh] overflow-y-auto space-y-4 custom-scrollbar">
- {balanceDetailLoading ? (
- <div className="py-12 flex justify-center text-slate-500">
- 数据加载中...
- </div>
- ) : balanceDetailError ? (
- <div className="rounded-2xl border border-rose-500/20 bg-rose-500/10 p-4 text-sm text-rose-400">
- {balanceDetailError}
- </div>
- ) : balanceRecords.length === 0 ? (
- <div className="py-16 flex flex-col items-center justify-center rounded-3xl border border-dashed border-white/10 bg-white/5 text-slate-500">
- <p className="text-sm">暂无消费明细</p>
- </div>
- ) : (
- <div className="space-y-3">
- {balanceRecords.map((r) => (
- <div
- key={r.id}
- className="rounded-[1.5rem] border border-white/5 bg-white/5 p-5 transition-all hover:bg-white/10"
- >
- <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
- <div>
- <p className="text-[15px] font-bold text-white">
- {balanceTypeLabel(r.type)}
- </p>
- <p className="mt-1 text-xs font-medium text-slate-500">
- 添加时间: {r.addTime || "-"}
- </p>
- </div>
- <div className="text-right">
- <p className="text-xl font-bold text-white tracking-tight">
- ${Number(r.amount ?? 0).toFixed(2)}
- </p>
- <span className="inline-block mt-1 rounded-full border border-white/10 bg-white/5 px-3 py-0.5 text-[10px] font-bold uppercase tracking-widest text-slate-400">
- type: {r.type}
- </span>
- </div>
- </div>
- </div>
- ))}
- </div>
- )}
- </div>
- </div>
- </div>
- )}
- {/* 修改信息弹框 */}
- {editInfoOpen && (
- <div className="fixed inset-0 z-[65] flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
- <div className="w-full max-w-md overflow-hidden rounded-[2.5rem] border border-white/10 bg-[#0a1120] shadow-2xl">
- <div className="flex items-center justify-between border-b border-white/5 p-6 md:px-8">
- <h3 className="text-xl font-bold text-white flex items-center gap-2">
- <UserRoundPen className="text-[#b89458]" size={20} /> 修改信息
- </h3>
- <button
- type="button"
- onClick={closeEditInfo}
- disabled={editSubmitting}
- className="text-sm font-bold text-slate-500 hover:text-white disabled:opacity-50"
- >
- 关闭
- </button>
- </div>
- <div className="p-6 md:p-8 space-y-4">
- {editError ? (
- <div className="rounded-2xl border border-rose-500/20 bg-rose-500/10 p-4 text-sm text-rose-400">
- {editError}
- </div>
- ) : null}
- <div className="space-y-4">
- <div>
- <label className="text-sm font-bold text-slate-300">姓名</label>
- <input
- value={editName}
- onChange={(e) => setEditName(e.target.value)}
- className="mt-2 w-full rounded-xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white placeholder-slate-500 focus:border-[#b89458] focus:outline-none focus:ring-1 focus:ring-[#b89458]"
- placeholder="请输入姓名"
- />
- </div>
- <div>
- <label className="text-sm font-bold text-slate-300">手机号</label>
- <input
- value={editPhone}
- onChange={(e) => setEditPhone(e.target.value)}
- className="mt-2 w-full rounded-xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white placeholder-slate-500 focus:border-[#b89458] focus:outline-none focus:ring-1 focus:ring-[#b89458]"
- placeholder="请输入手机号"
- />
- </div>
- <div>
- <label className="text-sm font-bold text-slate-300">证件号</label>
- <input
- value={editIdentity}
- onChange={(e) => setEditIdentity(e.target.value)}
- className="mt-2 w-full rounded-xl border border-white/10 bg-black/20 px-4 py-3 text-sm text-white placeholder-slate-500 focus:border-[#b89458] focus:outline-none focus:ring-1 focus:ring-[#b89458]"
- placeholder="请输入证件号"
- />
- </div>
- </div>
- </div>
- <div className="flex gap-4 border-t border-white/5 p-6 bg-black/20">
- <button
- type="button"
- onClick={closeEditInfo}
- disabled={editSubmitting}
- className="flex-1 rounded-xl bg-white/10 py-4 font-bold text-white hover:bg-white/20 transition-all disabled:opacity-50"
- >
- 取消
- </button>
- <button
- type="button"
- onClick={submitEditInfo}
- disabled={editSubmitting}
- className="flex-1 rounded-xl bg-gradient-to-br from-[#f3deae] to-[#d9be88] py-4 font-bold text-[#5c461a] shadow-lg hover:opacity-90 transition-all disabled:opacity-50"
- >
- {editSubmitting ? "提交中..." : "确认修改"}
- </button>
- </div>
- </div>
- </div>
- )}
- {/* 退出登录确认弹框 */}
- {logoutConfirmOpen && (
- <div className="fixed inset-0 z-[80] flex items-center justify-center bg-[#050b14]/80 p-4 backdrop-blur-md">
- <div className="w-full max-w-sm overflow-hidden rounded-[2.5rem] border border-white/10 bg-[#0a1120] shadow-2xl">
- <div className="p-10 text-center">
- <div className="mx-auto mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-rose-500/10 text-rose-400">
- <LogOut size={28} />
- </div>
- <h3 className="text-xl font-bold text-white">确认退出登录?</h3>
- <p className="mt-3 text-sm leading-relaxed text-slate-400 px-4">
- 退出后将清空本地缓存信息。
- </p>
- </div>
- <div className="flex border-t border-white/5">
- <button
- type="button"
- onClick={() => setLogoutConfirmOpen(false)}
- className="flex-1 py-5 text-sm font-bold text-slate-400 hover:bg-white/5"
- >
- 取消
- </button>
- <div className="w-px bg-white/5" />
- <button
- type="button"
- onClick={() => {
- setLogoutConfirmOpen(false);
- logout();
- if (typeof window !== "undefined") window.location.reload();
- }}
- className="flex-1 py-5 text-sm font-bold text-rose-400 hover:bg-rose-500/10"
- >
- 确认退出
- </button>
- </div>
- </div>
- </div>
- )}
- </div>
- );
- }
|